; Clip v1.0(Bate)

#Requires AutoHotkey v2.0

#Include tool\FileTool.ahk
#Include tool\SimLog.ahk
#Include tool\CalcVar.ahk

; CSV 读写
#Include tool\TextProcessor.ahk
#Include tool\CSV_Pcr.ahk

#Include tool\chinese\ChNum.ahk

; 图形界面
#Include gui\OptWindow.ahk
#Include gui\AuxWindow.ahk

; 额外扩展(热字符串)
#Include tool\FD_HotStrings.ahk
#Include tool\FD_HotStrings_clip.ahk

; 脚本核心对象
clip := {
    ; 程序名和版本号
    clipVer: "Clip v1.1(Bate)",
    ; 是否启用检查文件更新(0 不启用, 1 启用)
    checkFileEnabled: 1,
    ; 是否启用粘贴板监听(0 不启用, 1 启用)
    clipCallEnabled: 1,
    ; 保存粘贴板到数据文件(0 不启用, 1 启用)
    clipWriteDataFile: 0,
    ; 是否启用热键功能(0 不启用, 1 启用)
    ctrlCEnabled: 0,
    ; 是否启用热键功能(0 不启用, 1 启用)
    F6Enabled: 1,
    ; 是否启用热键功能(0 不启用, 1 启用)
    ctrlF6Enabled: 1,
    ; 是否启用热键功能(0 不启用, 1 启用)
    F7Enabled: 1,
    ; 是否启用热键功能(0 不启用, 1 启用)
    ctrlF7Enabled: 1,
    ; 是否启用热键功能(0 不启用, 1 启用)
    ctrlVEnabled: 0,
    ; 是否启用热键功能(0 不启用, 1 启用)
    ctrlREnabled: 0,
    ; 是否启用热键功能(0 不启用, 1 启用)
    ctrlAltQEnabled: 1,
    ; 第一目录(应用程序所在路径)
    firstDir: A_WorkingDir,
    ; 第二目录(存放文件配置，粘贴记录...)
    secondDir: EnvGet("USERPROFILE") "\faintdream\clip",
    ; 配置文件路径
    configFile: "\Clip.ini",
    ; 数据文件路径
    dataFile: "\data.txt",
    ; 附加应用程序父路径
    AddonParent: "\Add_on_Application",
    ; 粘贴板数据备份文件路径(clip20240620.temp)
    copyFile: "\record\clipRecord" FormatTime("", "yyyyMMdd") ".temp",
    ; 日志文件文件路径
    logFile: "\log\ClipLog.log",
    ; 粘贴方式(目前支持两种：seed 击键， clip 粘贴板)
    pauseMethod: "clip",
    ; 粘贴文本
    texts: [],
    ; 上一次粘贴的文本
    lastText: "",
    ; 下一次将要粘贴的文本
    nextText: "",
    ; 可变粘贴文本索引
    textsVarIndex: [],
    ; 数据文件为空时粘贴的默认文本
    defText: "#无数据#",
    ; 初始索引为 1（AutoHotkey 数组从 1 开始）
    index: 1,
    ; 是否保留索引（1 保留，0 不保留）
    keepIndex: 0,
    ; 上次检测的文件大小
    dataFileSize: 0,
    ; 数据文件是否改变（0 未改变，1 已改变）
    dataFileChanged: 0,
    ; 数据文件原文
    dataFileText: "",
    ; 数据模型(LIST 序列, AUTO-INCREMENT(Auto) 自增, CSV csv表格 暂不支持，后面会添加)
    dataModel: "LIST",
    ; 数据模型不可改变(0 可以改变, 1 不可改变)
    dataModelFinal: 0,
    ; 外协文本编辑(默认notepad.exe 记事本)
    ex_edit: "notepad.exe"
}

; 数据文件原文
dataFileText := "" ; 读取数据文件(在ClipCore.init()中读取)

; 初始化
ClipCore.init()

; 自定义热键 Ctrl + R
; ^r:: ClipCore.repeat("^r")

; 自定义热键 Ctrl + V
; ^v:: ClipCore.modelPaste("^v")

;; 切换主要功能是否启用 [F6]
;F6:: ClipCore.toggleFeatureEn("F6")

;; 显示设置窗口 [Ctrl + F6]
;^F6:: ClipCore.showOptWindow("^F6")

; 自定义热键 F7
;F7:: CustomF7("F7")

; 自定义热键 Ctrl + F7
;^F7:: CustomCtrlF7("^F7")

; ----- 函数(回调) -----


; 按下 F7 执行的事件
CustomF7(hotKeyName) {

    global clip ; 声明全局变量

    ToolTip(
        "↓↓下次粘贴:" " [" clip.index "/" clip.texts.length "]" "`n"
        clip.nextText
    )
    SetTimer () => ToolTip(), -2000
}

; 按下 Ctrl + F7 执行的事件
CustomCtrlF7(hotKeyName) {
    AuxWindow.toggle()
}

; ----- 函数(固定) -----

; 读取文件内容并初步处理数据
C_readFile(filePath) {
    global clip ; 声明全局变量
    fileContent := ClipCore.file.read(filePath)  ; 读取文件内容

    ; 分析数据模型
    C_isDataModel(fileContent)

    ; AUTO-INCREMENT模式的处理方式
    if (clip.dataModel = "AUTO-INCREMENT") {
        C_auto_incrementHandle(fileContent)
        return fileContent
    }

    ; LIST模式的处理方式
    if (clip.dataModel = "LIST") {

        clip.texts := [] ; texts 初始化为空数组
        lines := StrSplit(fileContent, "`n") ; 按行分割文件内容

        ; 去除每行的换行符并检查空行
        for index, line in lines {
            trimmedLine := Trim(line, "`r`n`t ")
            if (trimmedLine != "") {
                clip.texts.Push(trimmedLine)
            }
        }

        ; 没有读到数据(将默认文本写入)
        if (clip.texts.Length = 0) {
            clip.texts.Push(clip.defText)
        }

        ; 索引重置(如果需要)
        if (clip.keepIndex = 0) {
            clip.index := 1
        }

        return fileContent
    }

    ; 一般执行不到这里
    return fileContent
}

; 检查数据文件是否发生变化
C_checkDataFile() {

    global clip ; 声明全局变量
    global dataFileText ; 声明全局变量

    ; 不检查文件变化
    if (clip.checkFileEnabled == 0) {
        return 0 ;
    }

    ; 确定文件已经改变
    if (clip.dataFileChanged == 1) {
        clip.dataFileText := ClipCore.file.read(Clip.dataFile)
        return 1
    }

    return ClipCore.checkFileChange(Clip.dataFile, &dataFileText)
}
; 分析数据模型
C_isDataModel(fileContent) {

    global clip ; 声明全局变量
    global dataFileText ; 声明全局变量

    if (!SubStr(dataFileText, 1, 1) = "#") {
        clip.dataModel := "LIST"
        return
    }

    dataModel := "" ;从数据文件寻找数据模式
    loop Parse dataFileText {
        ; MsgBox(A_Index " : " A_LoopField)
        dataModel := dataModel A_LoopField
        if (A_LoopField == "#" && A_Index != 1) {
            break
        }
    }
    if (clip.dataModelFinal = 0) {
        if (InStr(dataModel, "#AUTO#")) {
            clip.dataModel := "AUTO-INCREMENT"
        } else if (InStr(dataModel, "#AUTO-INCREMENT#")) {
            clip.dataModel := "AUTO-INCREMENT"
        } else {
            ; 默认数据模型为LIST
            clip.dataModel := "LIST"
        }

    }
}

; 处理LIST模型
C_list() {

    global clip ; 声明全局变量
    global dataFileText ; 声明全局变量

    ; 格式化后发送当前索引对应的文本(模仿粘贴)
    temp := clip.texts[clip.index]
    ClipCore.paste(temp)

    ; 将本次的粘贴文本复制一份(给其他功能使用 Ctrl + R)
    clip.lastText := temp

    ; 将索引递增
    clip.index++

    ; 如果索引超出数组长度，重新设置为 1
    if (clip.index > clip.texts.Length) {
        clip.index := 1
    }

    ; 下次粘贴的内容
    temp := clip.texts[clip.index]

    ; 复制一份(给其他功能使用 F7)
    clip.nextText := temp

}

; 处理AUTO-INCREMENT模型
C_auto_increment() {

    global clip ; 声明全局变量
    static first := true ; 是否是第一次执行

    isVar := 0 ; 是否是变量字符串
    temp := ""

    if (first) {
        loop clip.texts.Length {

            Index1 := A_Index

            ; 判断是否是变量文本
            loop clip.textsVarIndex.Length {

                if (Index1 == clip.textsVarIndex[A_Index]) {
                    isVar := 1 ; 如果是标记跳过本次循环
                    break
                }
            }

            if (isVar == 1) { ;是变量字符串

                isVar := 0 ; 重新设置为是普通字符串

                ; 计算变量文本
                temp := temp CalcVar.calc(clip.texts[A_Index])
                continue
            }

            ; 普通字符串不需要特殊处理
            temp := temp clip.texts[A_Index]
        }

        ; 标记下次不要再执行
        first := false

        ; 下次粘贴的内容
        clip.nextText := temp

        ; 将本次的粘贴文本复制一份(给其他功能使用 Ctrl + R)
        clip.lastText := temp

        ; 赋初值
        isVar := 0 ; 是否是变量字符串
        temp := ""
    }

    ; 发送当前索引对应的文本(模仿粘贴)
    ; 首次执行时计算两次，输出第一次的计算结果，并把第二次计算的结果保存
    ; 后面执行直接输出上次计算结果，然后计算一次，并把计算的结果保存，流给后面使用
    ClipCore.paste(clip.nextText)

    ; ----- -----

    ; 将本次的粘贴文本复制一份(给其他功能使用 Ctrl + R)
    clip.lastText := clip.nextText

    loop clip.texts.Length {

        Index1 := A_Index

        ; 判断是否是变量文本
        loop clip.textsVarIndex.Length {

            if (Index1 == clip.textsVarIndex[A_Index]) {
                isVar := 1 ; 如果是标记跳过本次循环
                break
            }
        }

        if (isVar == 1) { ;是变量字符串

            isVar := 0 ; 重新设置为是普通字符串

            ; 计算变量文本
            temp := temp CalcVar.calc(clip.texts[A_Index])
            continue
        }

        ; 普通字符串不需要特殊处理
        temp := temp clip.texts[A_Index]
    }

    ; 下次粘贴的内容
    clip.nextText := temp

}

; 处理AUTO-INCREMENT模型(具体实现)
C_auto_incrementHandle(Str) {

    global clip ; 声明全局变量
    clip.texts := [] ; texts 初始化为空数组

    static variable := false ; 判断字符是属于变量字符还是常规字符
    static StrN := "" ; 常规字符串
    static StrV := "" ; 变量字符串

    local markRow := "" ; 第一行标识数据

    ; 删除无效字符(第一行标识数据视为无效字符)
    loop parse Str {
        if (A_LoopField == "`r" || A_LoopField == "`n") {
            markRow := SubStr(Str, 1, A_Index)
            Str := SubStr(Str, A_Index)
            break
        }

    }

    ; 分析标识字符(待完善)
    ; MsgBox "Str:" . Str
    ; MsgBox "markRow:" . markRow

    Str := Trim(Str, "`r`n`t ") ; 删除无效字符

    loop parse, Str {

        if (A_LoopField = "{") {
            variable := true
            clip.texts.Push(StrN)
            StrN := ""
        }

        if (variable) {
            StrV .= A_LoopField
        } else {
            StrN .= A_LoopField
        }

        if (A_LoopField = "}") {
            variable := false
            clip.texts.Push(Trim(StrV, "{}"))
            StrV := ""
            clip.textsVarIndex.Push(clip.texts.Length)
        }


    }
    clip.texts.Push(StrN) ;将剩下字符存入
    StrN := ""
    StrV := ""

    CalcVar.reset() ; 变量重置
}

; 退出脚本时执行
OnExit((r, c) => ClipCore.onExit(r, c))


; 脚本核心方法(大概)
class ClipCore {

    ; ### 脚本运行时执行 ###


    ; 初始化
    static init() {

        global clip ; 声明全局变量
        global dataFileText ; 声明全局变量

        ; 如果第二路径不存在，创建它
        if (DirExist(clip.secondDir)) {
            DirCreate(clip.secondDir)
        }

        ; 加上父路径(第二路径)
        clip.copyFile := clip.secondDir . clip.copyFile
        this.file.read(clip.copyFile)

        clip.logFile := clip.secondDir . clip.logFile
        this.file.read(clip.logFile)

        clip.configFile := clip.secondDir . clip.configFile
        this.file.read(clip.configFile)

        ; 读取配置(配置文件在第二路径下)
        this.readConf()

        ; 加上父路径(第一路径)
        clip.dataFile := clip.firstDir . clip.dataFile
        this.file.read(clip.dataFile)

        ; 加上父路径(第一路径)
        clip.AddonParent := clip.firstDir . clip.AddonParent


        ;;;;; ;;;;; ;;;;; ;;;;; ;;;;; ;;;;; ;;;;; ;;;;; ;;;;; ;;;;;


        ; 写日志
        this.logger.logPath := clip.logFile
        this.logger.log(clip.clipVer . ": 开始运行")

        ; 附加任务
        this.Addon()

        ; 读取数据文件
        dataFileText := ClipCore.file.read(Clip.dataFile)
        C_readFile(clip.dataFile)


        ; 设置托盘图标显示的文本
        if (clip.ctrlVEnabled) {
            A_IconTip := clip.clipVer . ": 粘贴功能已启用~"
        } else {
            A_IconTip := clip.clipVer . ": 粘贴功能已禁用~"
        }

        ; 绑定回调事件(粘贴板发生变化)
        OnClipboardChange((dataType) => this.ClipChanged(dataType))


        ; 绑定热键(待完善)


        ;; 自定义热键 Ctrl + V
        t := clip.ctrlVEnabled ? "On" : "Off"
        Hotkey("^v", (k) => ClipCore.modelPaste(k), t)

        ;; 自定义热键 Ctrl + R
        t := clip.ctrlREnabled ? "On" : "Off"
        Hotkey("^R", (k) => ClipCore.repeat(k), t)

        ;; 切换主要功能是否启用 [F6]
        t := clip.F6Enabled ? "On" : "Off"
        Hotkey("F6", (k) => ClipCore.toggleFeatureEn(k), t)

        ;; 显示设置窗口 [Ctrl + F6]
        t := clip.ctrlF6Enabled ? "On" : "Off"
        Hotkey("^F6", (k) => ClipCore.showOptWindow(k), t)


        ;; 自定义热键 [F7]
        t := clip.F7Enabled ? "On" : "Off"
        Hotkey("F7", (k) => CustomF7(k), t)

        ;; 自定义热键 [Ctrl + F7]
        t := clip.ctrlF7Enabled ? "On" : "Off"
        Hotkey("^F7", (k) => CustomCtrlF7(k), t)

    }

    ; 读取配置
    static readConf() {
        ; clip.clipVer := IniRead(clip.configFile, "clip", "clipVer", clip.clipVer)
        clip.checkFileEnabled := IniRead(clip.configFile, "clip", "checkFileEnabled", clip.checkFileEnabled)
        clip.clipCallEnabled := IniRead(clip.configFile, "clip", "clipCallEnabled", clip.clipCallEnabled)
        clip.clipWriteDataFile := IniRead(clip.configFile, "clip", "clipWriteDataFile", clip.clipWriteDataFile)

        clip.F6Enabled := IniRead(clip.configFile, "clip", "F6Enabled", clip.F6Enabled)
        clip.ctrlF6Enabled := IniRead(clip.configFile, "clip", "ctrlF6Enabled", clip.ctrlF6Enabled)
        clip.F7Enabled := IniRead(clip.configFile, "clip", "F7Enabled", clip.F7Enabled)
        clip.ctrlF7Enabled := IniRead(clip.configFile, "clip", "ctrlF7Enabled", clip.ctrlF7Enabled)
        clip.ctrlVEnabled := IniRead(clip.configFile, "clip", "ctrlVEnabled", clip.ctrlVEnabled)
        clip.ctrlREnabled := IniRead(clip.configFile, "clip", "ctrlREnabled", clip.ctrlREnabled)
        clip.ctrlAltQEnabled := IniRead(clip.configFile, "clip", "ctrlAltQEnabled", clip.ctrlAltQEnabled)

        clip.ex_edit := IniRead(clip.configFile, "clip", "ex_edit", clip.ex_edit)

    }

    ; 写入配置
    static writeConf() {
        IniWrite(clip.clipVer, clip.configFile, "clip", "clipVer")
        IniWrite(clip.checkFileEnabled, clip.configFile, "clip", "checkFileEnabled")
        IniWrite(clip.clipCallEnabled, clip.configFile, "clip", "clipCallEnabled")
        IniWrite(clip.clipWriteDataFile, clip.configFile, "clip", "clipWriteDataFile")

        IniWrite(clip.F6Enabled, clip.configFile, "clip", "F6Enabled")
        IniWrite(clip.ctrlF6Enabled, clip.configFile, "clip", "ctrlF6Enabled")
        IniWrite(clip.F7Enabled, clip.configFile, "clip", "F7Enabled")
        IniWrite(clip.ctrlF7Enabled, clip.configFile, "clip", "ctrlF7Enabled")
        IniWrite(clip.ctrlVEnabled, clip.configFile, "clip", "ctrlVEnabled")
        IniWrite(clip.ctrlREnabled, clip.configFile, "clip", "ctrlREnabled")

        IniWrite(clip.ex_edit, clip.configFile, "clip", "ex_edit")

    }

    ; 附加程序(执行)
    static Addon() {

        ; msgbox(clip.clipVer . ": (测试): " . clip.AddonParent . "\*")
        ; this.logger.log(clip.clipVer . "(测试): " . clip.AddonParent . "\*")

        Loop Files, clip.AddonParent . "\*", "F" {
            this.logger.log(clip.clipVer . ": 附加任务(尝试运行): " . A_LoopFileFullPath)
            Run(A_LoopFileFullPath)
        }

    }

    ; 附加程序(退出)
    static AddonExit() {
        Loop Files, clip.AddonParent . "\*", "F" {
            this.logger.log(clip.clipVer . ": 附加任务(尝试退出): " . A_LoopFileFullPath)
            Run(A_LoopFileFullPath . " -exit")
        }

    }

    ; ### 热键回调 ###


    ; 替代粘贴(程序主要功能) [ctrl + V]
    static modelPaste(hotKeyName) {

        global clip ; 声明全局变量

        ; 判断数据文件是否发生改变
        if (C_checkDataFile()) {
            C_readFile(clip.dataFile) ; 发生改变，重新读取文件
        }

        ; 判断使用什么模式处理数据文件
        switch clip.dataModel {
            case "LIST": C_list()
            case "AUTO": C_auto_increment()
            case "AUTO-INCREMENT": C_auto_increment()
            default: C_list()
        }


    }

    ; 重复上一次粘贴 [ctrl + R]
    static repeat(HotkeyName) {
        ClipCore.paste(clip.lastText)
    }

    ; 显示设置窗口 [Ctrl + F6]
    static showOptWindow(hotKeyName) {
        OptWindow.toggle()
    }

    ; 切换主要功能是否启用 [F6]
    static toggleFeatureEn(hotKeyName) {

        global clip ; 声明全局变量
        hotKeyEnabled := clip.ctrlVEnabled

        ; 切换
        if (hotKeyEnabled == 1) {
            hotKeyEnabled := 0
            ToolTip("粘贴功能已禁用~")
            SetTimer () => ToolTip(), -2000

        } else {
            hotKeyEnabled := 1
            ToolTip("粘贴功能已启用~")
            SetTimer () => ToolTip(), -2000
        }

        ; 设置
        if (hotKeyEnabled == 1) {
            clip.ctrlVEnabled := 1
            Hotkey("^v", (k) => ClipCore.modelPaste(k), "ON")
            clip.ctrlREnabled := 1
            Hotkey("^r", (k) => ClipCore.repeat(k), "ON")
            A_IconTip := clip.clipVer . ": 粘贴功能已启用~"
        } else {
            clip.ctrlVEnabled := 0
            Hotkey("^v", (k) => ClipCore.modelPaste(k), "OFF")
            clip.ctrlREnabled := 0
            Hotkey("^r", (k) => ClipCore.repeat(k), "OFF")
            A_IconTip := clip.clipVer . ": 粘贴功能已禁用~"
        }

        ; 告诉图形设置界面执行结果(无论窗口是否显示)
        OptWindow._assignment()


    }


    ; 粘贴板发生变化时执行的事件 [OnClipboardChange()]
    static clipChanged(dataType) {

        global clip ; 声明全局变量
        static runNum := 0

        if (clip.clipCallEnabled == 0) {
            Return
        }


        ; 写入数据文件
        if (clip.clipWriteDataFile) {
            ; 第一次运行，写入一个换行
            if (runNum = 0) {
                ClipCore.file.write(Clip.dataFile, "`r`n")
            }
            ; 将粘贴数据写入数据文件
            ClipCore.file.write(Clip.dataFile, A_Clipboard)

            ; 写入换行
            ClipCore.file.write(Clip.dataFile, "`r`n")
        }

        ; 写入备份文件
        {
            ; 第一次运行，写入一个换行
            if (runNum = 0) {
                ClipCore.file.write(Clip.copyFile, "`r`n")
            }
            ; 将粘贴数据写入数据文件
            ClipCore.file.write(Clip.copyFile, A_Clipboard)

            ; 写入换行
            ClipCore.file.write(Clip.copyFile, "`r`n")
        }

        runNum++

    }

    ; 退出脚本时执行 [OnExit()]
    static onExit(reason, code) {

        ; 保存修改的配置
        this.writeConf()

        ; 尝试关闭附加的程序
        this.AddonExit()

        ; 写日志
        this.logger.log(clip.clipVer . ": 退出程序")
    }

    ; 退出脚本(程序)并显示提示
    static exit(tip := unset) {

        if (IsSet(tip)) {
            MsgBox(tip)
        }
        this.logger.log(clip.clipVer . "异常退出程序: " . tip)
        ExitApp(1) ;退出脚本
    }

    ; 检查文件是否变化的函数(filePath 文件路径，fileText上次检查时的文件文本)
    static checkFileChange(filePath, &fileText) {
        last := fileText ; 旧值赋给临时变量
        fileText := this.file.read(filePath) ; 获取新值
        if (last == fileText) { ; 比较
            return 0
        }
        return 1
    }


    ; 处理粘贴的函数
    static paste(text) {
        switch (clip.pauseMethod) {
            case "send": this._send(text)
            case "clip": this._clip(text)
            default: this._send(text)
        }
    }

    ; send模式(击键)
    static _send(text) {
        SendText(text)
    }

    ; clip模式(粘贴板)
    static _clip(text) {
        if (clip.clipWriteDataFile) {
            clip.clipWriteDataFile := 0 ; 关闭粘贴监听(否则会有BUG)
        }

        A_Clipboard := text ; 将文本放入粘贴板
        Hotkey("^v", (k) => ClipCore.modelPaste(k), "OFF") ; 临时关闭自定义快捷键
        Send("^v") ; 使用系统默认的粘贴事件
        Hotkey("^v", (k) => ClipCore.modelPaste(k), "ON") ; 开启自定义的快捷键
    }

    ; 读取文本对象
    static file := FileTool()

    ; 日志对象
    static logger := SimLog()
}